import re
import os.path
import logging
import yaml
import sqlite3

from .iopin import IO_Pin
from .iodb import IOCheck_Database

regex_ini_format = re.compile(r"^(#*)\s*([\w,\.]*)\s*=\s*([\w,\.]*)\s*#*\s*(\w*)", re.IGNORECASE)
"""
    PLC.QX1.0				= PLC.MB3019.0		#轴选Z
    PLC.QX1.1				= PLC.MB3063.0		#润滑
"""

regex_turning_format = re.compile(r"^-*\s*(#*)\s*([\w,\.]*)\s*:\s*([\w,\.]*)\s*#*\s*(\w*)", re.IGNORECASE)
"""
- PLC.MB2200.0: PLC.IX9.4    #急停
  PLC.MB2212.0: PLC.IX9.3    #主轴风扇辅助触点
"""

# regex_milling_format  # 对于 milling.yaml 配置文件，通过yaml解析
def parse_milling_config_file(path):
    """
    - PLC.MB2004.0:
        - PLC.IX6.1#重复执行
    PLC.MB2023.0:
        - PLC.IX3.1#轴选X+
    """
    with open(path) as fp:
        py_data = yaml.load(fp)

    def parse_yaml_data(yaml_data):
        list_file_items = []
        if not isinstance(yaml_data, list):
            raise Exception("parse_milling_config_file() -->> \
                             Except! 无法识别的Milling-Yaml格式（外层数据格式应为数组）")
        for dict_yaml in yaml_data:  # 子层数据格式为字典
            for key, list_value in dict_yaml.items():
                if not isinstance(list_value, list):
                    raise Exception("parse_milling_config_file() -->> \
                                     Except! 非Milling-Yaml格式（value数据格式应为数组）")

                value = list_value[0]
                if value.find("#") >= 0:
                    output_addr, str_intro = value.split('#', maxsplit=1)
                else:
                    output_addr = value
                    str_intro = ""

                list_item = [
                    key,
                    output_addr,
                    str_intro,
                    False
                ]
                list_file_items.append(list_item)  # tuple(list_item)

        return list_file_items

    return parse_yaml_data(py_data)


class IOCheck_SQLite(IOCheck_Database):
    # def __new__(cls, *args):
    #     # usually, this static method return super().__new__(cls); but here:
    #     if not hasattr(cls, "__hasinstance__"):
    #         cls.instance = super().__new__(cls)
    #         cls.__hasinstance__ = False
    #     return cls.instance

    def __init__(self, path_file=None):
    #     if self.__hasinstance__ == False:
    #         self.__hasinstance__ = True
        super().__init__()

        # the singleton initialize
        if path_file:
            self.sqlite_prepare(path_file)

    def sqlite_prepare(self, path):
        """ 创建 or 连接数据库，并创建数据表 """
        file_ = os.path.split(path)[1]
        file_name, _ = os.path.splitext(file_)
        try:
            self.db_create_table(file_name)
        except sqlite3.OperationalError:
            # 若存在同名数据表
            self.db_drop_table(file_name)
            self.db_create_table(file_name)

    @classmethod
    def str2iopinTuple(cls, str_iopin):
        iopin = IO_Pin(str_iopin)
        tuple_iopin = (
            str_iopin,
            iopin.maintype,
            iopin.subtype,
            iopin.untype
        )
        return tuple_iopin

    # @override
    def maps_insert(self, tuple_item):
        super().maps_insert(self.curr_table, tuple_item)
        # 检查key, 并添加至: self.curr_table + self.key_suffix
        super().maps_insert(self.curr_table + self.key_suffix, self.str2iopinTuple(tuple_item[0]))
        # 检查value, 并添加至: self.curr_table + self.value_suffix
        super().maps_insert(self.curr_table + self.value_suffix, self.str2iopinTuple(tuple_item[1]))


def load_config_file(path: str):
    """ 载入一个yaml文件，正则解析，并在数据库中创建一张对应的table
        return: IOCheck_SQLite object
    """
    if not os.path.exists(path):
        raise Exception("IO_Dict::load_from_file() -->> Except! No such file [{}]".format(path))

    try:  # 尝试 milling_yaml_format 解析
        list_maps_data = parse_milling_config_file(path)
    except:
        # import traceback
        # traceback.print_exc()
        list_maps_data = parse_regex_config_file(path)

    def sqlite_iopins_analyze(list_maps, isKey):
        list_iopins = []
        for tuple_map in list_maps:
            str_iopin = tuple_map[0] if isKey else tuple_map[1]
            list_iopins.append(IOCheck_SQLite.str2iopinTuple(str_iopin))
        return list_iopins

    try:
        MDB = IOCheck_SQLite(path)
        MDB.maps_import(list_maps_data)
        MDB.maps_import(sqlite_iopins_analyze(list_maps_data, True), MDB.key_suffix)
        MDB.maps_import(sqlite_iopins_analyze(list_maps_data, False), MDB.value_suffix)
    except sqlite3.IntegrityError as e:
        logging.exception("load_config_file() -->> Fail to import file-data into the sqlite【{}】".format(e))

    return MDB


def parse_regex_config_file(path):
    """ return a LIST of tuple(key_addr, value_addr, str_intro, nonuse: bool) """
    _, ext = os.path.splitext(path)
    if ext == ".yaml":
        regex_compiler = regex_turning_format
    elif ext == ".ini":
        regex_compiler = regex_ini_format
    else:
        raise Exception("parse_regex_config_file() -->> Except! 无法识别的后缀名【{}】".format(ext))

    list_file_items = []
    with open(path) as fp:
        for strline in fp.readlines():
            re_match = regex_compiler.match(strline)
            if not re_match:
                continue
            else:
                re_match_groups = re_match.groups()
            list_item = [
                re_match_groups[1],
                re_match_groups[2],
                re_match_groups[3] if re_match_groups[3] else None,
                True if re_match_groups[0] else False
            ]

            list_file_items.append(list_item)  # tuple(list_item)

    return list_file_items


def dump_to_turning_yaml(MDB, path):
    list_maps = MDB.maps_export_all()

    with open(path, "w") as fp:
        first_line = True
        for map_item in list_maps:
            if first_line:
                line_tab = "- "
                first_line = False
            else:
                line_tab = "  "
            nonuse = "# " if map_item[3] == 1 else ""
            comment = "  #{}".format(map_item[2]) if map_item[2] else ""

            str_line = "{}{}{}: {}{}\n".format(line_tab, nonuse, map_item[0], map_item[1], comment)
            fp.write(str_line)


if __name__ == "__main__":
    logging.basicConfig(level=logging.DEBUG)

    MDB = load_config_file("test/plc_io_i5os.yaml")
    # MDB = load_config_file("test/plc_io_milling.yaml")
    # MDB = load_config_file("test/plc_io.ini")

    list_maps = MDB.maps_export()  # "value_addr"
    print(list_maps)

    list_maps = MDB.maps_export(where="key_sub_prefix='QX'")
    print(list_maps)
